<!DOCTYPE html>
<html lang="zh_cn">
<head>
          <title>来玩魔王的咚</title>
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <meta charset="utf-8" />
        <!-- twitter card metadata -->
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="/images/mowang.png">
<meta name="twitter:site" content="">
<meta name="twitter:title" content="Go 函数">
<meta name="twitter:description" content="<p>裸返回；图的遍历；捕获迭代变量；defer；panic；recover</p>">
        <!-- OG Tags -->
<meta property="og:url" content="/gopl-functions.html"/>
<meta property="og:title" content="来玩魔王的咚 | Go 函数" />
<meta property="og:description" content="<p>裸返回；图的遍历；捕获迭代变量；defer；panic；recover</p>" />
        <!-- favicon -->
        <link rel="icon" type="image/png" href="/images/mowang.png">
        <!-- moment.js for date formatting -->
        <script src="/theme/js/moment.js"></script>
        <!-- css -->
        <link rel="stylesheet" type="text/css" href="/theme/css/main.css" />
        <!-- 左边的menu，如果页面高度不够，就跟着滚动，否则文章分类显示不全 -->
        <link rel="stylesheet" type="text/css" href="/theme/css/mycss/menu.css" />
		<script>
			
                /*! grunt-grunticon Stylesheet Loader - v2.1.2 | https://github.com/filamentgroup/grunticon | (c) 2015 Scott Jehl, Filament Group, Inc. | MIT license. */
    
    (function(e){function t(t,n,r,o){"use strict";function a(){for(var e,n=0;u.length>n;n++)u[n].href&&u[n].href.indexOf(t)>-1&&(e=!0);e?i.media=r||"all":setTimeout(a)}var i=e.document.createElement("link"),l=n||e.document.getElementsByTagName("script")[0],u=e.document.styleSheets;return i.rel="stylesheet",i.href=t,i.media="only x",i.onload=o||null,l.parentNode.insertBefore(i,l),a(),i}var n=function(r,o){"use strict";if(r&&3===r.length){var a=e.navigator,i=e.Image,l=!(!document.createElementNS||!document.createElementNS("http://www.w3.org/2000/svg","svg").createSVGRect||!document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#Image","1.1")||e.opera&&-1===a.userAgent.indexOf("Chrome")||-1!==a.userAgent.indexOf("Series40")),u=new i;u.onerror=function(){n.method="png",n.href=r[2],t(r[2])},u.onload=function(){var e=1===u.width&&1===u.height,a=r[e&&l?0:e?1:2];n.method=e&&l?"svg":e?"datapng":"png",n.href=a,t(a,null,null,o)},u.src="",document.documentElement.className+=" grunticon"}};n.loadCSS=t,e.grunticon=n})(this);(function(e,t){"use strict";var n=t.document,r="grunticon:",o=function(e){if(n.attachEvent?"complete"===n.readyState:"loading"!==n.readyState)e();else{var t=!1;n.addEventListener("readystatechange",function(){t||(t=!0,e())},!1)}},a=function(e){return t.document.querySelector('link[href$="'+e+'"]')},c=function(e){var t,n,o,a,c,i,u={};if(t=e.sheet,!t)return u;n=t.cssRules?t.cssRules:t.rules;for(var l=0;n.length>l;l++)o=n[l].cssText,a=r+n[l].selectorText,c=o.split(");")[0].match(/US\-ASCII\,([^"']+)/),c&&c[1]&&(i=decodeURIComponent(c[1]),u[a]=i);return u},i=function(e){var t,o,a;o="data-grunticon-embed";for(var c in e)if(a=c.slice(r.length),t=n.querySelectorAll(a+"["+o+"]"),t.length)for(var i=0;t.length>i;i++)t[i].innerHTML=e[c],t[i].style.backgroundImage="none",t[i].removeAttribute(o);return t},u=function(t){"svg"===e.method&&o(function(){i(c(a(e.href))),"function"==typeof t&&t()})};e.embedIcons=i,e.getCSS=a,e.getIcons=c,e.ready=o,e.svgLoadedCallback=u,e.embedSVG=u})(grunticon,this);
                
                grunticon(["/theme/css/icons.data.svg.css", "/theme/css/icons.data.png.css", "/theme/css/icons.fallback.css"]);
            </script>
        <noscript><link href="/theme/css/icons.fallback.css" rel="stylesheet"></noscript>
        <!-- menu toggle javascript -->
        <script type="text/javascript">
            document.addEventListener("DOMContentLoaded", initMenu);
            
            function initMenu(){
                var menu = document.getElementById("menu");
                var menulink = document.getElementById("menu-link");
                menulink.addEventListener("click", function toggleMenu(){
                        window.event.preventDefault();
                        menulink.classList.toggle('active');
                        menu.classList.toggle('active');              
                    });
            };
        </script>
        <!-- 不蒜子 -->
        <script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>

    <meta name="description" content="<p>裸返回；图的遍历；捕获迭代变量；defer；panic；recover</p>" />

    <meta name="tags" content="gopl" />
  <!-- 替换部分base的样式，看文章时，要再宽一点，右边有很多空间可以撑开 -->
  <link rel="stylesheet" type="text/css" href="/theme/css/mycss/article.css" />

</head>
<body>
    <div role="banner" id="masthead">
        <header>
            <a href="/"><img src="/images/mowang.png" alt="McManus Logo"></a>
                <h1>来玩魔王的咚@骑士救兵</h1>
            <a href="#menu" id="menu-link">more stuff</a>
            <nav id="menu">
                <ul>
                        <li><a href="/tags">tags</a></li>
                            <li><a href="/category/cloud.html">Cloud</a></li>
                            <li><a href="/category/docker.html">Docker</a></li>
                            <li class="active"><a href="/category/go.html">Go</a></li>
                            <li><a href="/category/linux.html">Linux</a></li>
                            <li><a href="/category/python.html">Python</a></li>
                            <li><a href="/category/xue-xi-bi-ji.html">学习笔记</a></li>
                            <li><a href="/category/yun-wei-zi-dong-hua.html">运维自动化</a></li>
                </ul>
            </nav>
        </header>
    </div>
        <div class="page" role="main">
  <div class="article" role="article">
    <article>
        <footer>
            <a name="top"></a>
            <p>
              <time datetime=" 2020-07-14 16:00:00+08:00">
                <script>document.write(moment('2020-07-14 16:00:00+08:00').format('LL'));</script>
              </time>
              ~
              <time datetime=" 2020-07-14 16:00:00+08:00">
                <script>document.write(moment('2020-07-14 16:00:00+08:00').format('LL'));</script>
              </time>
            </p>
        </footer>
        <header>
          <h2>
            Go 函数
          </h2>
        </header>
      <div class="content">
         <div class="toc">
<ul>
<li><a href="#luo-fan-hui">裸返回</a></li>
<li><a href="#tu-de-bian-li">图的遍历</a></li>
<li><a href="#jing-gao-bu-huo-die-dai-bian-liang">警告：捕获迭代变量</a><ul>
<li><a href="#goroutine-zhong-tong-yang-de-wen-ti">goroutine 中同样的问题</a></li>
</ul>
</li>
<li><a href="#yan-chi-han-shu-diao-yong-defer">延迟函数调用（defer）</a></li>
<li><a href="#panic-yi-chang">Panic异常</a><ul>
<li><a href="#zhu-dong-diao-yong-panic">主动调用 panic</a></li>
<li><a href="#zhuan-chu-zhan-xin-xi">转储栈信息</a></li>
</ul>
</li>
<li><a href="#recover-bu-huo-yi-chang">Recover捕获异常</a><ul>
<li><a href="#recover-han-shu">recover函数</a></li>
<li><a href="#ju-li-shuo-ming">举例说明</a></li>
<li><a href="#hui-fu-de-yuan-ze">恢复的原则</a></li>
<li><a href="#lian-xi">练习</a></li>
</ul>
</li>
</ul>
</div>
<h3 id="luo-fan-hui"><a class="toclink" href="#luo-fan-hui">裸返回</a></h3>
<p>一个函数如果有命名的返回值，可以省略 return 语句的操作数（只有return，后面不跟任何内容），这称为<strong>裸返回</strong>。<br>
在一个函数中如果存在许多返回语句且有多个返回结果，裸返回可以消除重复代码，但是并不能使代码更加易于理解。<br>
比如，对于这种方式，在第一眼看来，不能直观地看出返回的值具体是什么。如果之前一直没有使用过返回值的变量名，则返回变量的零值。如果赋过值了，则返回新的值。这就有可能会看漏。鉴于这个原因，应该保守使用裸返回。<br>
裸返回的例子:</p>
<div class="highlight"><pre><span></span><code><span class="kd">func</span><span class="w"> </span><span class="nx">split</span><span class="p">(</span><span class="nx">sum</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nx">x</span><span class="p">,</span><span class="w"> </span><span class="nx">y</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="nx">x</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">sum</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="mi">9</span><span class="w"></span>
<span class="w">    </span><span class="nx">y</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">sum</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="nx">x</span><span class="w"></span>
<span class="w">    </span><span class="k">return</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>

<p>保守的使用，所以不用也很好。但是概念要知道，能看懂别人写的裸返回。  </p>
<h3 id="tu-de-bian-li"><a class="toclink" href="#tu-de-bian-li">图的遍历</a></h3>
<p>在下面的例子中，变量 prereqs 的 map 提供了很多课程（key），以及学习该课程的前置条件（value）：</p>
<div class="highlight"><pre><span></span><code><span class="kd">var</span><span class="w"> </span><span class="nx">prereqs</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">][]</span><span class="kt">string</span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="s">&quot;algorithems&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="s">&quot;data structures&quot;</span><span class="p">},</span><span class="w"></span>
<span class="w">    </span><span class="s">&quot;calculus&quot;</span><span class="p">:</span><span class="w">    </span><span class="p">{</span><span class="s">&quot;linear algebra&quot;</span><span class="p">},</span><span class="w"></span>
<span class="w">    </span><span class="s">&quot;compilers&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="s">&quot;data structures&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w">        </span><span class="s">&quot;formal languages&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w">        </span><span class="s">&quot;computer organization&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w">    </span><span class="p">},</span><span class="w"></span>
<span class="w">    </span><span class="s">&quot;data structures&quot;</span><span class="p">:</span><span class="w">       </span><span class="p">{</span><span class="s">&quot;discrete math&quot;</span><span class="p">},</span><span class="w"></span>
<span class="w">    </span><span class="s">&quot;databases&quot;</span><span class="p">:</span><span class="w">             </span><span class="p">{</span><span class="s">&quot;data structures&quot;</span><span class="p">},</span><span class="w"></span>
<span class="w">    </span><span class="s">&quot;discrete math&quot;</span><span class="p">:</span><span class="w">         </span><span class="p">{</span><span class="s">&quot;intro to programming&quot;</span><span class="p">},</span><span class="w"></span>
<span class="w">    </span><span class="s">&quot;formal languages&quot;</span><span class="p">:</span><span class="w">      </span><span class="p">{</span><span class="s">&quot;discrete math&quot;</span><span class="p">},</span><span class="w"></span>
<span class="w">    </span><span class="s">&quot;networks&quot;</span><span class="p">:</span><span class="w">              </span><span class="p">{</span><span class="s">&quot;operating systems&quot;</span><span class="p">},</span><span class="w"></span>
<span class="w">    </span><span class="s">&quot;operating systems&quot;</span><span class="p">:</span><span class="w">     </span><span class="p">{</span><span class="s">&quot;data structures&quot;</span><span class="p">,</span><span class="w"> </span><span class="s">&quot;computer organization&quot;</span><span class="p">},</span><span class="w"></span>
<span class="w">    </span><span class="s">&quot;programming languages&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="s">&quot;data structures&quot;</span><span class="p">,</span><span class="w"> </span><span class="s">&quot;computer organization&quot;</span><span class="p">},</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>

<p><strong>图</strong><br>
这样的问题是一种拓扑排序。概念上，先决条件的内容构成了一张有向图，每一个节点代表一门课程。每一条边代表一门课程所依赖的另一门课程的关系。<br>
图是无环的：没有节点可以通过图上的路径回到它自己。  </p>
<p>可以使用深度优先的搜索计算得到合法的学习路径，代码入下所示：</p>
<div class="highlight"><pre><span></span><code><span class="kd">func</span><span class="w"> </span><span class="nx">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="nx">i</span><span class="p">,</span><span class="w"> </span><span class="nx">course</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">topoSort</span><span class="p">(</span><span class="nx">prereqs</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Printf</span><span class="p">(</span><span class="s">&quot;%d:\t%s\n&quot;</span><span class="p">,</span><span class="w"> </span><span class="nx">i</span><span class="o">+</span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="nx">course</span><span class="p">)</span><span class="w"></span>
<span class="w">    </span><span class="p">}</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>

<span class="kd">func</span><span class="w"> </span><span class="nx">topoSort</span><span class="p">(</span><span class="nx">m</span><span class="w"> </span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">][]</span><span class="kt">string</span><span class="p">)</span><span class="w"> </span><span class="p">[]</span><span class="kt">string</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="c1">// 闭包的部分</span><span class="w"></span>
<span class="w">    </span><span class="kd">var</span><span class="w"> </span><span class="nx">order</span><span class="w"> </span><span class="p">[]</span><span class="kt">string</span><span class="w"></span>
<span class="w">    </span><span class="nx">seen</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">bool</span><span class="p">)</span><span class="w"></span>
<span class="w">    </span><span class="kd">var</span><span class="w"> </span><span class="nx">visitAll</span><span class="w"> </span><span class="kd">func</span><span class="p">(</span><span class="nx">items</span><span class="w"> </span><span class="p">[]</span><span class="kt">string</span><span class="p">)</span><span class="w"></span>
<span class="w">    </span><span class="nx">visitAll</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="kd">func</span><span class="p">(</span><span class="nx">items</span><span class="w"> </span><span class="p">[]</span><span class="kt">string</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="k">for</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">item</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">items</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">            </span><span class="k">if</span><span class="w"> </span><span class="p">!</span><span class="nx">seen</span><span class="p">[</span><span class="nx">item</span><span class="p">]</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">                </span><span class="nx">seen</span><span class="p">[</span><span class="nx">item</span><span class="p">]</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="kc">true</span><span class="w"></span>
<span class="w">                </span><span class="nx">visitAll</span><span class="p">(</span><span class="nx">m</span><span class="p">[</span><span class="nx">item</span><span class="p">])</span><span class="w"></span>
<span class="w">                </span><span class="nx">order</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nb">append</span><span class="p">(</span><span class="nx">order</span><span class="p">,</span><span class="w"> </span><span class="nx">item</span><span class="p">)</span><span class="w"></span>
<span class="w">            </span><span class="p">}</span><span class="w"></span>
<span class="w">        </span><span class="p">}</span><span class="w"></span>
<span class="w">    </span><span class="p">}</span><span class="w"></span>
<span class="w">    </span><span class="c1">// 主体</span><span class="w"></span>
<span class="w">    </span><span class="kd">var</span><span class="w"> </span><span class="nx">keys</span><span class="w"> </span><span class="p">[]</span><span class="kt">string</span><span class="w"></span>
<span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="nx">key</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">m</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="nx">keys</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nb">append</span><span class="p">(</span><span class="nx">keys</span><span class="p">,</span><span class="w"> </span><span class="nx">key</span><span class="p">)</span><span class="w"></span>
<span class="w">    </span><span class="p">}</span><span class="w"></span>
<span class="w">    </span><span class="nx">sort</span><span class="p">.</span><span class="nx">Strings</span><span class="p">(</span><span class="nx">keys</span><span class="p">)</span><span class="w"></span>
<span class="w">    </span><span class="nx">visitAll</span><span class="p">(</span><span class="nx">keys</span><span class="p">)</span><span class="w"></span>
<span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="nx">order</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>

<p>当一个匿名函数需要进行递归，必须先声明一个变量然后将匿名函数赋给这个变量。如果将两个步骤合并成一个声明，函数字面量将不会存在于该匿名函数的作用域中，这样就不能递归地调用自己了。  </p>
<p>下面是拓扑排序的程序输出，它是确定的结果，就是每次执行都一样。这里输出时调用的是切片而不是 map，所以迭代的顺序是确定的并且在调用最初的 map 之前是对它的 key 进行了排序的。  </p>
<div class="highlight"><pre><span></span><code>PS H:\Go\src\gopl\ch5\toposort&gt; go run main.go
1:      intro to programming
2:      discrete math
3:      data structures
4:      algorithems
5:      linear algebra
6:      calculus
7:      formal languages
8:      computer organization
9:      compilers
10:     databases
11:     operating systems
12:     networks
13:     programming languages
PS H:\Go\src\gopl\ch5\toposort&gt;
</code></pre></div>

<h3 id="jing-gao-bu-huo-die-dai-bian-liang"><a class="toclink" href="#jing-gao-bu-huo-die-dai-bian-liang">警告：捕获迭代变量</a></h3>
<p>首先，看下面的代码：</p>
<div class="highlight"><pre><span></span><code><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w"></span>

<span class="kn">import</span><span class="w"> </span><span class="s">&quot;fmt&quot;</span><span class="w"></span>

<span class="kd">func</span><span class="w"> </span><span class="nx">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="kd">var</span><span class="w"> </span><span class="nx">shows</span><span class="w"> </span><span class="p">[]</span><span class="kd">func</span><span class="p">()</span><span class="w"></span>
<span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="p">[]</span><span class="kt">int</span><span class="p">{</span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="mi">4</span><span class="p">,</span><span class="w"> </span><span class="mi">5</span><span class="p">}</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="nx">shows</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nb">append</span><span class="p">(</span><span class="nx">shows</span><span class="p">,</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="nx">v</span><span class="p">)</span><span class="w"> </span><span class="p">})</span><span class="w"></span>
<span class="w">    </span><span class="p">}</span><span class="w"></span>

<span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">f</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">shows</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="nx">f</span><span class="p">()</span><span class="w"></span>
<span class="w">    </span><span class="p">}</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>

<p>这里的期望是依次打印每个数。但实际打印出来的全部都是5。  </p>
<p>在for循环引进的一个块作用域内声明了变量v，然后到了循环里使用的这个变量名共享相同的变量，即一个可访问的存储位置，而不是固定的值。v的值在不断地迭代中更新，因此当之后调用打印的时候，v变量已经被每一次的for循环更新多次。所以打印出来的是最后一次迭代时的值。<br>
这里可以通过引入一个内部变量来解决这个问题，可以换个名字，也可以使用一样的变量名：</p>
<div class="highlight"><pre><span></span><code><span class="kd">func</span><span class="w"> </span><span class="nx">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="kd">var</span><span class="w"> </span><span class="nx">shows</span><span class="w"> </span><span class="p">[]</span><span class="kd">func</span><span class="p">()</span><span class="w"></span>
<span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="p">[]</span><span class="kt">int</span><span class="p">{</span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="mi">4</span><span class="p">,</span><span class="w"> </span><span class="mi">5</span><span class="p">}</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">v</span><span class="w"> </span><span class="c1">// 这句是关键</span><span class="w"></span>
<span class="w">        </span><span class="nx">shows</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nb">append</span><span class="p">(</span><span class="nx">shows</span><span class="p">,</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="nx">v</span><span class="p">)</span><span class="w"> </span><span class="p">})</span><span class="w"></span>
<span class="w">    </span><span class="p">}</span><span class="w"></span>

<span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">f</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">shows</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="nx">f</span><span class="p">()</span><span class="w"></span>
<span class="w">    </span><span class="p">}</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>

<p>看起来奇怪，但却是一个关键性的声明。for循环内也可以随意定义一个不一样的变量名，这样看着更好理解一些。<br>
也可以用匿名函数（闭包）来理解，这里确实是一个闭包，匿名函数内引用了外部变量。第一个示例中，变量v会在for循环的每次迭戈中更新。第二个示例，匿名函数引用的变量v是在for循环内部声明的，不会随着迭代而更新，并且在for循环内部也没有变化过。<br>
这样的隐患不仅仅存在于使用range的for循环里。在 <code>for i := 0; i &lt; 10; i++ {}</code> 这样的循环里作用域也是同样的，这里的变量i也是会有同样的问题，需要避免。<br>
另外在go语句和derfer语句的使用当中，迭代变量捕获的问题是最频繁的，这是因为这两个逻辑都会推迟函数的执行时机，直到循环结束。但是这个问题并不是有go或者defer语句造成的。  </p>
<h4 id="goroutine-zhong-tong-yang-de-wen-ti"><a class="toclink" href="#goroutine-zhong-tong-yang-de-wen-ti">goroutine 中同样的问题</a></h4>
<p>下面的用法是错误的：</p>
<div class="highlight"><pre><span></span><code><span class="k">for</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">f</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">names</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="nx">call</span><span class="p">(</span><span class="nx">f</span><span class="p">)</span><span class="w"> </span><span class="c1">// 注意：不正确</span><span class="w"></span>
<span class="w">    </span><span class="p">}</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>

<p>需要作为一个字面量函数的显式参数传递 f，而不是在 for 循环中声明 f。正确的做法如下：</p>
<div class="highlight"><pre><span></span><code><span class="k">for</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">f</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">names</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">(</span><span class="nx">f</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="nx">call</span><span class="p">(</span><span class="nx">f</span><span class="p">)</span><span class="w"></span>
<span class="w">    </span><span class="p">}(</span><span class="nx">f</span><span class="p">)</span><span class="w"> </span><span class="c1">// 显式的传递 f 给函数</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>

<p>像上面这样，通过添加显式参数，可以确保当 go 语句执行的时候，使用 f 的当前值。  </p>
<h3 id="yan-chi-han-shu-diao-yong-defer"><a class="toclink" href="#yan-chi-han-shu-diao-yong-defer">延迟函数调用（defer）</a></h3>
<p>defer 语句也可以用来调试一个复杂的函数，即在函数的“入口”和“出口”处设置调试行为。下面的 bigSlowOperation 函数在开头调用 trace 函数，在函数刚进入的时候执行输出，然后返回一个函数变量，当其被调用的时候执行退出函数的操作。以这种方式推迟返回函数的调用，就可以使一个语句在函数入口和所有出口添加处理，甚至可以传递一些有用的值。<br>
比如每个操作的开始时间：</p>
<div class="highlight"><pre><span></span><code><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w"></span>

<span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w"></span>
<span class="w">    </span><span class="s">&quot;log&quot;</span><span class="w"></span>
<span class="w">    </span><span class="s">&quot;time&quot;</span><span class="w"></span>
<span class="p">)</span><span class="w"></span>

<span class="kd">func</span><span class="w"> </span><span class="nx">bigSlowOperation</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="k">defer</span><span class="w"> </span><span class="nx">trace</span><span class="p">(</span><span class="s">&quot;bigSlowOperation&quot;</span><span class="p">)()</span><span class="w">  </span><span class="c1">// 这个小括号很重要</span><span class="w"></span>
<span class="w">    </span><span class="c1">// ...这里假设有一些操作...</span><span class="w"></span>
<span class="w">    </span><span class="nx">time</span><span class="p">.</span><span class="nx">Sleep</span><span class="p">(</span><span class="mi">3</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">)</span><span class="w"> </span><span class="c1">// 模拟慢操作</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>

<span class="kd">func</span><span class="w"> </span><span class="nx">trace</span><span class="p">(</span><span class="nx">msg</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="nx">start</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Now</span><span class="p">()</span><span class="w"></span>
<span class="w">    </span><span class="nx">log</span><span class="p">.</span><span class="nx">Printf</span><span class="p">(</span><span class="s">&quot;enter %s&quot;</span><span class="p">,</span><span class="w"> </span><span class="nx">msg</span><span class="p">)</span><span class="w"></span>
<span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">log</span><span class="p">.</span><span class="nx">Printf</span><span class="p">(</span><span class="s">&quot;exit %s (%s)&quot;</span><span class="p">,</span><span class="w"> </span><span class="nx">msg</span><span class="p">,</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Since</span><span class="p">(</span><span class="nx">start</span><span class="p">))</span><span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>

<span class="kd">func</span><span class="w"> </span><span class="nx">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="nx">bigSlowOperation</span><span class="p">()</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>

<p>通常的defer语句提供一个函数，会在函数退出时再调用。<br>
上面的defer语句，最后面有两个小括号。trace函数调用后会返回一个匿名函数，加上后面的小括号才是延迟调用执行的部分。而trace函数本身则会在当前位置就执行，并且返回匿名函数给defer语句。在trace函数获取返回值的过程中，也就是trace函数里，会先执行两行语句，获取start变量的值以及输出一行信息，这个是在函数开头就执行的。最后函数返回的匿名函数是提供给defer语句在退出的时候进行延迟调用的。  </p>
<h3 id="panic-yi-chang"><a class="toclink" href="#panic-yi-chang">Panic异常</a></h3>
<p>Go 语言的类型系统会在编译时捕获很多错误，但有些错误只能在运行时检查，如数组访问越界、空指针引用等。这些运行时错误会引起painc异常。  </p>
<h4 id="zhu-dong-diao-yong-panic"><a class="toclink" href="#zhu-dong-diao-yong-panic">主动调用 panic</a></h4>
<p>可以直接调用内置的 panic 函数。如果碰到“不可能发生”的状况，panic 是最好的处理方式，比如语句执行到逻辑上不可能到达的地方时。  </p>
<h4 id="zhuan-chu-zhan-xin-xi"><a class="toclink" href="#zhuan-chu-zhan-xin-xi">转储栈信息</a></h4>
<p>runtime 包提供了转储栈的方法是程序员可以诊断错误，下面的代码在 main 函数中延迟 printStack 的执行：</p>
<div class="highlight"><pre><span></span><code><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w"></span>

<span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w"></span>
<span class="w">    </span><span class="s">&quot;fmt&quot;</span><span class="w"></span>
<span class="w">    </span><span class="s">&quot;os&quot;</span><span class="w"></span>
<span class="w">    </span><span class="s">&quot;runtime&quot;</span><span class="w"></span>
<span class="p">)</span><span class="w"></span>

<span class="kd">func</span><span class="w"> </span><span class="nx">f</span><span class="p">(</span><span class="nx">x</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Printf</span><span class="p">(</span><span class="s">&quot;f(%d)\n&quot;</span><span class="p">,</span><span class="w"> </span><span class="nx">x</span><span class="o">+</span><span class="mi">0</span><span class="o">/</span><span class="nx">x</span><span class="p">)</span><span class="w"></span>
<span class="w">    </span><span class="k">defer</span><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Printf</span><span class="p">(</span><span class="s">&quot;defer %d\n&quot;</span><span class="p">,</span><span class="w"> </span><span class="nx">x</span><span class="p">)</span><span class="w"></span>
<span class="w">    </span><span class="nx">f</span><span class="p">(</span><span class="nx">x</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>

<span class="kd">func</span><span class="w"> </span><span class="nx">printStack</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="kd">var</span><span class="w"> </span><span class="nx">buf</span><span class="w"> </span><span class="p">[</span><span class="mi">4096</span><span class="p">]</span><span class="kt">byte</span><span class="w"></span>
<span class="w">    </span><span class="nx">n</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">runtime</span><span class="p">.</span><span class="nx">Stack</span><span class="p">(</span><span class="nx">buf</span><span class="p">[:],</span><span class="w"> </span><span class="kc">false</span><span class="p">)</span><span class="w"></span>
<span class="w">    </span><span class="nx">os</span><span class="p">.</span><span class="nx">Stdout</span><span class="p">.</span><span class="nx">WriteString</span><span class="p">(</span><span class="s">&quot;Stack 中的内容:\n&quot;</span><span class="p">)</span><span class="w"></span>
<span class="w">    </span><span class="nx">os</span><span class="p">.</span><span class="nx">Stdout</span><span class="p">.</span><span class="nx">Write</span><span class="p">(</span><span class="nx">buf</span><span class="p">[:</span><span class="nx">n</span><span class="p">])</span><span class="w"></span>
<span class="w">    </span><span class="nx">os</span><span class="p">.</span><span class="nx">Stdout</span><span class="p">.</span><span class="nx">WriteString</span><span class="p">(</span><span class="s">&quot;Stack 结束...\n&quot;</span><span class="p">)</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>

<span class="kd">func</span><span class="w"> </span><span class="nx">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="k">defer</span><span class="w"> </span><span class="nx">printStack</span><span class="p">()</span><span class="w"></span>
<span class="w">    </span><span class="nx">f</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>

<p>Panic之后，在退出前会调用 defer 的内容，输出 buf 中的栈信息。最后还会输出宕机消息到标准输出流。<br>
runtime.Stack 能够输出函数栈信息，在其他语言中，此时函数栈的信息应该已经不存在了。但是 Go 语言的宕机机制让延迟执行的函数在栈清理之前调用。  </p>
<h3 id="recover-bu-huo-yi-chang"><a class="toclink" href="#recover-bu-huo-yi-chang">Recover捕获异常</a></h3>
<p>退出程序通常是正常的处理panic异常的方式。但有时需要从异常中恢复，至少可以在程序崩溃前做一些操作。  </p>
<h4 id="recover-han-shu"><a class="toclink" href="#recover-han-shu">recover函数</a></h4>
<p>将内置的 recover 函数在延迟函数的内部调用，当定义了该 defer 语句的函数发生了 panic 异常，recover 就会终止当前的 panic 状态并且返回 panic value。函数不会从之前 panic 的地方继续运行而是正常返回。在未发生 panic 时调用 recover 则没有任何效果并且返回 nil。  </p>
<h4 id="ju-li-shuo-ming"><a class="toclink" href="#ju-li-shuo-ming">举例说明</a></h4>
<p>假设有一个语言解析器。即使看起来运行正常，但考虑到工作的复杂性，还是会存在只在特殊情况下发生的 bug。此时我们更希望返回一个错误 error 而不是导致程序崩溃 panic。所以 panic 发生后，不要立即终止运行，而是将一些有用的附加消息提供给用户来报告这个bug。<br>
下面是使用 recover 部分的代码：</p>
<div class="highlight"><pre><span></span><code><span class="kd">func</span><span class="w"> </span><span class="nx">Parse</span><span class="p">(</span><span class="nx">input</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nx">s</span><span class="w"> </span><span class="o">*</span><span class="nx">Syntax</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="kt">error</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="k">defer</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="nx">p</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">recover</span><span class="p">();</span><span class="w"> </span><span class="nx">p</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">            </span><span class="nx">err</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Errorf</span><span class="p">(</span><span class="s">&quot;internal error: %v&quot;</span><span class="p">,</span><span class="w"> </span><span class="nx">p</span><span class="p">)</span><span class="w"></span>
<span class="w">        </span><span class="p">}</span><span class="w"></span>
<span class="w">    </span><span class="p">}()</span><span class="w"></span>
<span class="w">    </span><span class="c1">// ...parser...</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>

<h4 id="hui-fu-de-yuan-ze"><a class="toclink" href="#hui-fu-de-yuan-ze">恢复的原则</a></h4>
<p>对于 panic 采用无差别的恢复措施是不可靠的。<br>
从同一个包内发生的 panic 进行恢复有助于简化处理复杂和未知的错误，但一般的原则是，不应该尝试去恢复从另一个包内发生的 panic。公共的 API 应该直接报告错误。同样，也不应该恢复一个 panic，而这段代码却不是由你来维护的，比如调用这提供的回调函数，因为你不清楚这样做是否安全。<br>
有时也很难完全遵循规范，举个例子，net\/http包中提供了一个web服务器，将收到的请求分发给用户提供的处理函数。很显然，我们不能因为某个处理函数引发的panic异常，影响整个进程导致退出。web服务器遇到处理函数导致的panic时会调用recover，输出堆栈信息，继续运行。这样的做法在实践中很便捷，但也会有一定的风险，比如导致资源泄漏或是因为recover操作，导致其他问题。<br>
所以，最安全的做法就是选择性地使用 recover。当 panic 之后需要进行恢复的情况本来就不多。为了标识某个 panic 是否应该被恢复，我们可以将 panic value 设置成特殊类型。在 recover 时对 panic value 进行检查，如果发现 panic value 是特殊类型，就将这个 panic 作为 errror 处理。如果不是，则按照正常的 panic 进行处理。<br>
下面示例代码中的 soleTitle 函数就是一个这样的例子：</p>
<div class="highlight"><pre><span></span><code><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w"></span>

<span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w"></span>
<span class="w">    </span><span class="s">&quot;fmt&quot;</span><span class="w"></span>
<span class="w">    </span><span class="s">&quot;net/http&quot;</span><span class="w"></span>
<span class="w">    </span><span class="s">&quot;os&quot;</span><span class="w"></span>
<span class="w">    </span><span class="s">&quot;strings&quot;</span><span class="w"></span>

<span class="w">    </span><span class="s">&quot;golang.org/x/net/html&quot;</span><span class="w"></span>
<span class="p">)</span><span class="w"></span>

<span class="kd">func</span><span class="w"> </span><span class="nx">forEachNode</span><span class="p">(</span><span class="nx">n</span><span class="w"> </span><span class="o">*</span><span class="nx">html</span><span class="p">.</span><span class="nx">Node</span><span class="p">,</span><span class="w"> </span><span class="nx">pre</span><span class="p">,</span><span class="w"> </span><span class="nx">post</span><span class="w"> </span><span class="kd">func</span><span class="p">(</span><span class="nx">n</span><span class="w"> </span><span class="o">*</span><span class="nx">html</span><span class="p">.</span><span class="nx">Node</span><span class="p">))</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">pre</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="nx">pre</span><span class="p">(</span><span class="nx">n</span><span class="p">)</span><span class="w"></span>
<span class="w">    </span><span class="p">}</span><span class="w"></span>
<span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="nx">c</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">n</span><span class="p">.</span><span class="nx">FirstChild</span><span class="p">;</span><span class="w"> </span><span class="nx">c</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="p">;</span><span class="w"> </span><span class="nx">c</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">c</span><span class="p">.</span><span class="nx">NextSibling</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="nx">forEachNode</span><span class="p">(</span><span class="nx">c</span><span class="p">,</span><span class="w"> </span><span class="nx">pre</span><span class="p">,</span><span class="w"> </span><span class="nx">post</span><span class="p">)</span><span class="w"></span>
<span class="w">    </span><span class="p">}</span><span class="w"></span>
<span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">post</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="nx">post</span><span class="p">(</span><span class="nx">n</span><span class="p">)</span><span class="w"></span>
<span class="w">    </span><span class="p">}</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>

<span class="c1">// soleTitle 返回文档中一个非空标题元素</span><span class="w"></span>
<span class="c1">// 如果没有标题则返回错误</span><span class="w"></span>
<span class="kd">func</span><span class="w"> </span><span class="nx">soleTitle</span><span class="p">(</span><span class="nx">doc</span><span class="w"> </span><span class="o">*</span><span class="nx">html</span><span class="p">.</span><span class="nx">Node</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nx">title</span><span class="w"> </span><span class="kt">string</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="kt">error</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="kd">type</span><span class="w"> </span><span class="nx">bailout</span><span class="w"> </span><span class="kd">struct</span><span class="p">{}</span><span class="w"></span>

<span class="w">    </span><span class="k">defer</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="k">switch</span><span class="w"> </span><span class="nx">p</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">recover</span><span class="p">();</span><span class="w"> </span><span class="nx">p</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="kc">nil</span><span class="p">:</span><span class="w"></span>
<span class="w">            </span><span class="c1">// 没有宕机</span><span class="w"></span>
<span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="nx">bailout</span><span class="p">{}:</span><span class="w"></span>
<span class="w">            </span><span class="c1">// 预期的宕机</span><span class="w"></span>
<span class="w">            </span><span class="nx">err</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Errorf</span><span class="p">(</span><span class="s">&quot;multiple title elements&quot;</span><span class="p">)</span><span class="w"></span>
<span class="w">        </span><span class="k">default</span><span class="p">:</span><span class="w"></span>
<span class="w">            </span><span class="nb">panic</span><span class="p">(</span><span class="nx">p</span><span class="p">)</span><span class="w"> </span><span class="c1">// 未预期的宕机，继续宕机过程</span><span class="w"></span>
<span class="w">        </span><span class="p">}</span><span class="w"></span>
<span class="w">    </span><span class="p">}()</span><span class="w"></span>
<span class="w">    </span><span class="c1">// 如果发现多余一个非空标题，退出递归</span><span class="w"></span>
<span class="w">    </span><span class="nx">forEachNode</span><span class="p">(</span><span class="nx">doc</span><span class="p">,</span><span class="w"> </span><span class="kd">func</span><span class="p">(</span><span class="nx">n</span><span class="w"> </span><span class="o">*</span><span class="nx">html</span><span class="p">.</span><span class="nx">Node</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="nx">n</span><span class="p">.</span><span class="nx">Type</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="nx">html</span><span class="p">.</span><span class="nx">ElementNode</span><span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span><span class="nx">n</span><span class="p">.</span><span class="nx">Data</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="s">&quot;title&quot;</span><span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span><span class="nx">n</span><span class="p">.</span><span class="nx">FirstChild</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">            </span><span class="k">if</span><span class="w"> </span><span class="nx">title</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="s">&quot;&quot;</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">                </span><span class="nb">panic</span><span class="p">(</span><span class="nx">bailout</span><span class="p">{})</span><span class="w"> </span><span class="c1">// 多个标题元素</span><span class="w"></span>
<span class="w">            </span><span class="p">}</span><span class="w"></span>
<span class="w">            </span><span class="nx">title</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">n</span><span class="p">.</span><span class="nx">FirstChild</span><span class="p">.</span><span class="nx">Data</span><span class="w"></span>
<span class="w">        </span><span class="p">}</span><span class="w"></span>
<span class="w">    </span><span class="p">},</span><span class="w"> </span><span class="kc">nil</span><span class="p">)</span><span class="w"></span>
<span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">title</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="s">&quot;&quot;</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;&quot;</span><span class="p">,</span><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Errorf</span><span class="p">(</span><span class="s">&quot;no title element&quot;</span><span class="p">)</span><span class="w"></span>
<span class="w">    </span><span class="p">}</span><span class="w"></span>
<span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="nx">title</span><span class="p">,</span><span class="w"> </span><span class="kc">nil</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>

<span class="kd">func</span><span class="w"> </span><span class="nx">title</span><span class="p">(</span><span class="nx">url</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="w"> </span><span class="kt">error</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="nx">resp</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">Get</span><span class="p">(</span><span class="nx">url</span><span class="p">)</span><span class="w"></span>
<span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w"></span>
<span class="w">    </span><span class="p">}</span><span class="w"></span>
<span class="w">    </span><span class="k">defer</span><span class="w"> </span><span class="nx">resp</span><span class="p">.</span><span class="nx">Body</span><span class="p">.</span><span class="nx">Close</span><span class="p">()</span><span class="w"></span>

<span class="w">    </span><span class="c1">// 检查返回的页面是HTML通过判断Content-Type，比如：Content-Type: text/html; charset=utf-8</span><span class="w"></span>
<span class="w">    </span><span class="nx">ct</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">resp</span><span class="p">.</span><span class="nx">Header</span><span class="p">.</span><span class="nx">Get</span><span class="p">(</span><span class="s">&quot;Content-Type&quot;</span><span class="p">)</span><span class="w"></span>
<span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">ct</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="s">&quot;text/html&quot;</span><span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span><span class="p">!</span><span class="nx">strings</span><span class="p">.</span><span class="nx">HasPrefix</span><span class="p">(</span><span class="nx">ct</span><span class="p">,</span><span class="w"> </span><span class="s">&quot;text/html;&quot;</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Errorf</span><span class="p">(</span><span class="s">&quot;%s has type %s, not text/html&quot;</span><span class="p">,</span><span class="w"> </span><span class="nx">url</span><span class="p">,</span><span class="w"> </span><span class="nx">ct</span><span class="p">)</span><span class="w"></span>
<span class="w">    </span><span class="p">}</span><span class="w"></span>

<span class="w">    </span><span class="nx">doc</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">html</span><span class="p">.</span><span class="nx">Parse</span><span class="p">(</span><span class="nx">resp</span><span class="p">.</span><span class="nx">Body</span><span class="p">)</span><span class="w"></span>
<span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Errorf</span><span class="p">(</span><span class="s">&quot;parseing %s as HTML: %v&quot;</span><span class="p">,</span><span class="w"> </span><span class="nx">url</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w"></span>
<span class="w">    </span><span class="p">}</span><span class="w"></span>

<span class="w">    </span><span class="nx">title</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">soleTitle</span><span class="p">(</span><span class="nx">doc</span><span class="p">)</span><span class="w"></span>
<span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w"></span>
<span class="w">    </span><span class="p">}</span><span class="w"></span>
<span class="w">    </span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="nx">title</span><span class="p">)</span><span class="w"></span>
<span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="kc">nil</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>

<span class="kd">func</span><span class="w"> </span><span class="nx">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">arg</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">os</span><span class="p">.</span><span class="nx">Args</span><span class="p">[</span><span class="mi">1</span><span class="p">:]</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">title</span><span class="p">(</span><span class="nx">arg</span><span class="p">);</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">            </span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Fprintf</span><span class="p">(</span><span class="nx">os</span><span class="p">.</span><span class="nx">Stderr</span><span class="p">,</span><span class="w"> </span><span class="s">&quot;title: %v\n&quot;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w"></span>
<span class="w">        </span><span class="p">}</span><span class="w"></span>
<span class="w">    </span><span class="p">}</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>

<p>defer 调用 recover，检查 panic value，如果该值是 bailout{} 则返回一个普通的错误。所有其他非空的值都是预料外的 panic，这时继续使用 panic value 的值作为参数调用 panic。  </p>
<p>这个示例里，违反了 panic 不处理"预期"错误的建议，但是这里是为了展示这种处理 panic 的机制：</p>
<div class="highlight"><pre><span></span><code><span class="k">if</span><span class="w"> </span><span class="nx">title</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="s">&quot;&quot;</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="nb">panic</span><span class="p">(</span><span class="nx">bailout</span><span class="p">{})</span><span class="w"> </span><span class="c1">// 多个标题元素</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>

<p>对于一个预期的错误，比如这里标题为空的情况。正常编写程序的时候，不应该调用panic，而是进行处理，比如返回 error。  </p>
<p>有些情况下是没有恢复动作的。比如，内存耗尽会使 Go 运行时发生严重错误而直接终止进程。  </p>
<h4 id="lian-xi"><a class="toclink" href="#lian-xi">练习</a></h4>
<p>使用 panic 和 recover 写一个函数，它没有 return 语句，但是能够返回一个非零的值。</p>
<div class="highlight"><pre><span></span><code><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w"></span>

<span class="kn">import</span><span class="w"> </span><span class="s">&quot;fmt&quot;</span><span class="w"></span>

<span class="kd">func</span><span class="w"> </span><span class="nx">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="nx">s</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">noRet</span><span class="p">()</span><span class="w"></span>
<span class="w">    </span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="nx">s</span><span class="p">)</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>

<span class="kd">func</span><span class="w"> </span><span class="nx">noRet</span><span class="p">()</span><span class="w"> </span><span class="p">(</span><span class="nx">s</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="k">defer</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="nx">p</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">recover</span><span class="p">()</span><span class="w"></span>
<span class="w">        </span><span class="nx">s</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Sprint</span><span class="p">(</span><span class="nx">p</span><span class="p">)</span><span class="w"></span>
<span class="w">    </span><span class="p">}()</span><span class="w"></span>
<span class="w">    </span><span class="nb">panic</span><span class="p">(</span><span class="s">&quot;Hello&quot;</span><span class="p">)</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
      </div>
      <div class="back-to-top">
        <a href="/">HOME</a>
        <a href="#top">TOP</a>
      </div>
    </article>
  </div>
<!-- end article -->
<!-- 页面往下滚动一段之后才会显示TOC -->
<script>
  window.onscroll = function() {
    var tocbox = document.getElementsByClassName('toc')[0];
    var osTop = document.documentElement.scrollTop || document.body.scrollTop;
    var osWidth = document.documentElement.scrollWidth || document.body.scrollWidth;
    // console.log(osTop)
    if (osTop>300 && osWidth>865) {
      tocbox.style.display = "block"
    }
    if (osTop<300 || osWidth<865) {
      tocbox.style.display = "none"
    }
  }
</script>
                <footer>
                    <div class="icons">
                    </div>
                    <span id="busuanzi_container_page_pv" style="padding: 10px">本文阅读量<span id="busuanzi_value_page_pv"></span>次</span>
                    <span id="busuanzi_container_site_pv" style="padding: 10px">本站总访问量<span id="busuanzi_value_site_pv"></span>次</span>
                    <span id="busuanzi_container_site_uv" style="padding: 10px">本站总访客数<span id="busuanzi_value_site_uv"></span>人</span>
                    <p>© <script>document.write(moment().format('YYYY'));</script> 749B</p>
                </footer>
        </div>
</body>
</html>